Github Repositories and R Packages

Dans nos différents jeux de données, nous avons pour l'instant :

  • Une liste des dépôts liés à R qui sont encore actifs en date du 17 février 2015. Ces dépôts ont été identifiés sur base des événements de type "PushEvent" qui se sont produits entre 2013 et 2014 (inclus).
  • Une liste des dépôts contenant un fichier DESCRIPTION à la racine du dépôt, ainsi que le contenu de ce fichier. Cela nous permet d'avoir une liste des paquets qui sont développés et/ou hébergés sur GitHub. Les fichiers ont été collectés le 17 février 2015.

Ces données nous permettent déjà d'obtenir beaucoup d'informations sur ce qui se passe sur Github pour les paquets R. Néanmoins, nous n'avons pas encore filtré les forks. En supprimant les forks, nous éviterons d'obtenir un même package R depuis plusieurs dépôts Github. Cela permettra d'avoir les informations du fichier DESCRIPTION depuis ce qui est probablement le dépôt principal, que nous espérons être le dépôt de développement de ce package.

Afin d'identifier quels sont les dépôts qui sont ou non des forks, il va falloir retravailler avec notre base MongoDB.


In [1]:
import pymongo
import pandas

conn = pymongo.MongoClient()

Notre collection "events" dans "R" contient tous les événements de 2013 et 2014. Nous allons filtrer les PushEvents, et récupérer les dépôts associés. Ensuite, nous associerons un flag "fork" pour indiquer si ce dépôt est un fork ou pas. L'objectif étant de recouper ces informations avec celles que nous possédons déjà.


In [2]:
rows = []

query = {'type': 'PushEvent', 'repository': {'$exists': True}}
project = ['repository.owner', 'repository.name', 'repository.url', 'repository.created_at', 'repository.pushed_at', 'repository.fork', 'repository.forks']
for item in conn.r.events.find(query, project):
    rows.append(item)

Cela peut prendre un peu de temps vu le nombre d'éléments à devoir traiter. Afin de pouvoir travailler avec les résultats, il convient d'adapter quelque peu la structure de données que nous avons récupérée :


In [3]:
len(rows), rows[:2]


Out[3]:
(683425,
 [{u'_id': ObjectId('54ec348ec9896e203c522fd4'),
   u'repository': {u'created_at': u'2011-03-23T16:40:49-07:00',
    u'fork': False,
    u'forks': 0,
    u'name': u'Papers',
    u'owner': u'ntustison',
    u'pushed_at': u'2013-05-26T07:08:21-07:00',
    u'url': u'https://github.com/ntustison/Papers'}},
  {u'_id': ObjectId('54ec348ec9896e2035522fd4'),
   u'repository': {u'created_at': u'2013-02-28T07:18:32-08:00',
    u'fork': False,
    u'forks': 0,
    u'name': u'lg',
    u'owner': u'lionup',
    u'pushed_at': u'2013-03-09T01:05:43-08:00',
    u'url': u'https://github.com/lionup/lg'}}])

Idéalement, nous voudrions :

  1. Supprimer les doublons pour le même owner et le même repository.
  2. Récupérer la valeur du fork la plus récente.
  3. Flatteniser les données tout en conservant la date de création, et idéalement, la dernière date connue d'événement.

In [4]:
def _flatten(row): 
    repository = row['repository']
    created_at = repository['created_at']
    owner = repository['owner']
    name = repository['name']
    url = repository['url']
    pushevent = repository['pushed_at']
    fork = repository['fork']
    forks = repository['forks']
    return {'owner': owner, 'repository': name, 'creation': created_at, 'last_push': pushevent, 'fork': fork, 'forks': forks}

df = pandas.DataFrame.from_dict(map(_flatten, rows))

Nous avons maintenant un joli tableau sur lequel il nous reste à supprimer les doublons et conserver la valeur la plus récente, pour chaque (owner, repository). Pandas va nous aider, avec drop_duplicates...


In [5]:
repos = df.drop_duplicates(('owner', 'repository'), take_last=True)

In [6]:
repos.to_csv('../data/R-Repositories.csv')

Nous avons précédemment identifier, pour chaque dépôt, lesquels étaient associés à un package R. Les données sont situées dans le fichier github-description.csv. Ce fichier contient, pour chaque couple (owner, repository) une liste des clés/valeurs du fichier DESCRIPTION.


In [12]:
descriptions = pandas.DataFrame.from_csv('../data/github-description.csv', index_col=None)
packages = descriptions.query('key == "Package"').rename(columns={'value': 'Package'})[['owner', 'repository', 'Package']]
authors = descriptions.query('key == "Author"').rename(columns={'value': 'Author'})[['owner', 'repository', 'Author']]
imports = descriptions.query('key == "Imports"').rename(columns={'value': 'Imports'})[['owner', 'repository', 'Imports']]
depends = descriptions.query('key == "Depends"').rename(columns={'value': 'Depends'})[['owner', 'repository', 'Depends']]

In [13]:
_ = packages.merge(repos, how='left', on=('owner', 'repository'))
_ = _.merge(authors, how='left', on=('owner', 'repository'))
_ = _.merge(imports, how='left', on=('owner', 'repository'))
df = _.merge(depends, how='left', on=('owner', 'repository'))

Certaines informations présentes dans le fichier github-description.csv semblent ne pas exister dans notre collection d'événements, probablement (à confirmer) parce que ce fichier a été créé sur base de tous les événements, pas seulements des PushEvent. Nous allons supprimer les éléments pour lesquels nous n'avons pas une telle information.


In [14]:
df = df.dropna(axis=0, subset=('creation',))

Enfin, nous pouvons choisir arbitrairement de ne considérer que les dépôts qui ne sont pas des forks, et pour chaque package apparaissant dans au moins deux dépôts, de ne garder que le dépôt avec la date de création la plus ancienne. L'étiquette canonical est ajouté sur chaque dépôt ainsi identifié. Avant de faire cette opération, nous allons retirer les packages associés à cran et rpkg (owners sur Github) car il s'agit d'un mirroring, et donc clairement pas de dépôts de développement. Notez que ces dépôts seront encore présents dans le .csv final, mais ils ne seront simplement pas pris en compte pour l'ajout de l'étiquette canonical.


In [15]:
_ = df.query('fork == False and owner != "cran" and owner != "rpkg"')
_ = _.sort(columns='creation')
_ = _.drop_duplicates('Package')
_['canonical'] = 1
df = df.merge(_[['owner', 'repository', 'canonical']], how='left', on=('owner', 'repository'))
df = df.fillna(value={'canonical': 0, 'Author': '', 'Imports': '', 'Depends': ''})

In [16]:
df.to_csv('../data/github-RPackage-repository.csv')